/*
FILENAME:     graph.cpp
PROJECT:      gplot
AUTHOR:       V.G.Ramanathan
EMAIL:        vgramanathan@gmail.com

LICENCE:      Free for all
VERSION:      1.01

REQUIREMENTS: You need to have the file glut.h in /usr/include/GL to re-compile
              You can, however run the pre-compiled program from the bin directory.
              To install glut on a debian-based system: sudo apt-get install freeglut3-dev
              For other systems, please search for freeglut-dev/mesaglut-dev
*/

#include <GL/glut.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cmath>

using namespace std;

#define MAXV 100
#define MAXE 50

#define PI 3.14159625

struct npair
{
  int x;
  int y;
};

int   directed=0,filemode=0,weights[MAXE],highlights[MAXE];
int   nedges,nvertices,picked=0,cx[MAXV],cy[MAXV];
int   red[MAXV],green[MAXV],blue[MAXV];
bool  regMouseCoords=false;
int   winwidth,winheight;
char  text[10],message[100];;
int   dragStart=0;
float radius=7.0f;
int   seed;
npair edges[MAXE];
FILE* fin;
bool  grid=false;
bool  snap=false;
int   snapWidth=10;
int   gridWidth=10;
int   loop=0;

void glutDraw()
{
  int i,j;
  float x1,x2,y1,y2;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);      
  glColor3ub(128,128,128);
  glBegin(GL_LINES);
  glVertex2i(0,0);
  glVertex2i(winwidth-1,0);
  glVertex2i(winwidth-1,0);
  glVertex2i(winwidth-1,20);
  glVertex2i(winwidth-1,20);
  glVertex2i(0,20);
  glVertex2i(0,20);
  glVertex2i(0,0);
  glEnd();
  if(grid)
    {
      glColor3ub(128,128,128);
      glBegin(GL_POINTS);
      for(i=0; i<winwidth; i+=gridWidth)
	for(j=20; j<winheight; j+=gridWidth)
	  glVertex2i(i,j);
      glEnd();
    }
  if(picked==nvertices)
    {
      glColor3ub(255,255,255);
      for(i=0; i<nedges; i++)
	{
	  if(highlights[i]==24 || highlights[i]==32 || highlights[i]==64 || highlights[i]==56)
	    glColor3ub(0,255,0);
	  else
	    glColor3ub(255,255,255);
	  x1=(float)cx[edges[i].x];
	  x2=(float)cx[edges[i].y];
	  y1=(float)(winheight-cy[edges[i].x]);
	  y2=(float)(winheight-cy[edges[i].y]);
	  glBegin(GL_LINES);
	  glVertex2f(x1,y1);
	  glVertex2f(x2,y2);
	  glEnd();
	  if(directed)
	    {
	      glPushMatrix();
	      glTranslatef(x2,y2,0.0f);
	      glRotatef(180.0f/PI*atan((y2-y1)/(x2-x1))-90.0f,0.0f,0.0f,1.0f);
	      if(x2>x1)
		glRotatef(180.0f,0.0f,0.0f,1.0f);
	      glTranslatef(0,radius,0);
	      glBegin(GL_TRIANGLES);
	      glVertex2f(0.0f,0.0f);
	      glVertex2f(-4.0f,12.0f);
	      glVertex2f(4.0f,12.0f);
	      glEnd();
	      glPopMatrix();
	    }
	}
      for(i=0; i<nedges; i++)
	{
	  if(weights[i]!=0)
	    {
	      glColor3ub(0,255,255);
	      glRasterPos2f((float)(cx[edges[i].x]+cx[edges[i].y])/2,winheight-(float)(cy[edges[i].x]+cy[edges[i].y]+10.0f)/2);
	      sprintf(text,"%i",weights[i]);
	      for(j=0; j<strlen(text); j++)
		glutBitmapCharacter(GLUT_BITMAP_8_BY_13,text[j]);
	    }
	}
    }
  for(i=0; i<nvertices; i++)
    {
      glPushMatrix();
      glTranslatef((float)cx[i],(float)(winheight-cy[i]),0.0f);
      glColor3ub(red[i],green[i],blue[i]);
      glutSolidSphere(radius,32,32);
      glColor3ub(0,0,0);
      sprintf(text,"%i",i+1);
      glRasterPos2f(-4.0f*strlen(text),-5.0f);
      for(j=0; j<strlen(text); j++)
	glutBitmapCharacter(GLUT_BITMAP_8_BY_13,text[j]);
      glPopMatrix();
    }
  glColor3ub(0,255,255);
  glRasterPos2f((float)winwidth/2-strlen(message)*4,5.0f);
  for(j=0; j<strlen(message); j++)
    glutBitmapCharacter(GLUT_BITMAP_8_BY_13,message[j]);
}
  
void glutIdle()
{
  usleep(10000);
  glutDraw();
  glutSwapBuffers();
}

void glutResize(int w, int h)
{
  if(h==0)
    h=1;
  glViewport(0,0,w,h);
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0,w,0,h,-100,100);
  winwidth=w;
  winheight=h;
}

void glutMouse(int button, int state, int x, int y)
{
  if(state==GLUT_UP)
    {
      regMouseCoords=false;
    }
  if(button!=GLUT_LEFT_BUTTON || state!=GLUT_DOWN || picked>=nvertices)
    return;
  cx[picked]=x;
  cy[picked]=y;
  sprintf(message,"Node %i placed at (%i,%i)",picked+1,x,winheight-y);
  picked++;
}

void glutSpecial(int key, int x, int y)
{
  if(key==GLUT_KEY_DOWN && radius>=7.0f)
      radius-=1.0f;
  else if(key==GLUT_KEY_UP && radius<=20.0f)
    radius+=1.0f;
  glutDraw();
}

void glutKeyboard(unsigned char key, int x, int y)
{
  if(picked<nvertices && (key=='R' || key=='r'))
    {
      cx[picked]=2+(int)radius+rand()%(winwidth-(int)radius-2);
      cy[picked]=2+(int)radius+rand()%(winheight-(int)radius-2);
      picked++;
    }
  if(key=='G' || key=='g')
    {
      grid=!grid;
      if(grid)
	sprintf(message,"Grid is on");
      else
	sprintf(message,"Grid id off");
    }
  if(key=='S' || key=='s')
    {
      snap=!snap;
      if(snap)
	sprintf(message,"Snap is on");
      else
	sprintf(message,"Snap is off");
    }
  if(key=='-' && grid && snapWidth>=10)
    {
      snapWidth--;
      gridWidth--;
    }
  else if(key=='+' && grid && snapWidth<=40)
    {
      snapWidth++;
      gridWidth++;
    }
}

void usage(char* prog)
{
  cout << prog << " [-h] [-d] [-f fn]\n";
  cout << "\t-h: Print usage\n"
       << "\t-d: Assume that graph is directed from x->y\n"
       << "\t-f fn: Get input from file fn\n";
}

void glutMotion(int x, int y)
{
  int i;
  if(picked!=nvertices)
    return;
  if(regMouseCoords==true)
    {
      if(snap)
	{
	  cx[dragStart]=x-x%snapWidth;
	  cy[dragStart]=y-y%snapWidth;
	}
      else
	{
	  cx[dragStart]=x;
	  cy[dragStart]=y;
	}
      sprintf(message,"Cursor is at (%i,%i)",x,winheight-y);
      return;
    }
  for(i=0; i<nedges; i++)
    {
      if((x-cx[i])*(x-cx[i])+(y-cy[i])*(y-cy[i])<=radius*radius)
	{
	  dragStart=i;
	  regMouseCoords=true;
	  return;
	}
    }
}

void parse_input(int &edgex, int &edgey, int &weight, int &mark)
{
  edgex=edgey=weight=mark=0;
  char dump=0;
  int num=0;
  int waschar=0;
  while(dump!='\n')
    {
      if(!filemode)
	dump=getc(stdin);
      else
	dump=getc(fin);
      if(dump==' ' || dump=='\n')
	{
	  if(waschar)
	    {
	      mark=num;
	      num=0;
	      continue;
	    }
	  if(edgex==0)
	    edgex=num;
	  else if(edgey==0)
	    edgey=num;
	  else if(weight==0)
	    weight=num;
	  else
	    mark=num;
	  num=0;
	}
      else
	{
	  if(isalpha(dump))
	    waschar=1;
	  else
	    waschar=0;
	  num=num*10+(dump-48);
	}
    }
}

int main(int argc, char** argv)
{
  char c;
  char temp[256];
  strcpy(temp,argv[1]);
  seed=time(0);
  srand(seed);
  while((c=getopt(argc,argv,"hdf:"))!=-1)
  {
    switch(c)
      {
      case 'h':
	usage(argv[0]);
	return 0;
	break;
      case 'd':
	directed=1;
	break;
      case 'f':
	fin=fopen(optarg,"r");
	if(!fin)
	  {
	    cerr << "No such file " << optarg << endl;
	    return 1;
	  }
	filemode=1;
	break;
      default:
	return 1;
      }
  }
  if(temp[0]!='-')
    {
      fin=fopen(temp,"r");
      if(!fin)
	{
	  cerr << "No such file " << temp << endl;
	  return 1;
	}
      filemode=1;
    }
  int i;
  for(i=0; i<MAXE; i++)
    highlights[i]=weights[i]=0;
  cout << "\nEnter number of vertices (1-100): ";
  if(!filemode)
    cin >> nvertices;
  else
    fscanf(fin,"%i",&nvertices);
  cout << "\nEnter number of edges (1-50): ";
  if(!filemode)
    cin >> nedges;
  else
    fscanf(fin,"%i",&nedges);
  cout << "\nEnter the edges in the format \"x y\" one per line:\n";
  if(!filemode)
    getc(stdin);
  else
    getc(fin);
  for(i=0; i<nedges; i++)
    {
      parse_input(edges[i].x,edges[i].y,weights[i],highlights[i]);
      edges[i].x--;
      edges[i].y--;
    }
  cout << "\nPick points to represent the vertices...\n\n";
  for(i=0; i<MAXV; i++)
    {
      cx[i]=cy[i]=-100;
      red[i]=128+rand()%128;
      green[i]=128+rand()%128;
      blue[i]=128+rand()%128;
    }
  strcpy(message,"Click to position a node");
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowPosition(112,84);
  glutInitWindowSize(800,600);
  glutCreateWindow("Graph");
  glutReshapeFunc(glutResize);
  glutDisplayFunc(glutDraw);
  glutIdleFunc(glutIdle);
  glutMouseFunc(glutMouse);
  glutMotionFunc(glutMotion);
  glutSpecialFunc(glutSpecial);
  glutKeyboardFunc(glutKeyboard);
  glutMainLoop();
  return 0;
}
